package run

import (
	"context"
	"fmt"
	"github.com/Unixeno/islands/cmd/connection"
	"github.com/Unixeno/islands/cmd/container"
	"github.com/Unixeno/islands/cmd/image"
	"github.com/Unixeno/islands/cmd/io"
	"github.com/Unixeno/islands/cmd/network"
	"github.com/Unixeno/islands/cmd/streams"

	//"github.com/Unixeno/islands/cmd/streams"
	"github.com/Unixeno/islands/cmd/utils"
	"github.com/Unixeno/islands/pb"
	"github.com/spf13/cobra"
	"net"
	"os"
	"regexp"
	"strconv"
	"sync"
)

var (
	interactive     bool
	tty             bool
	name            string
	detach          bool
	hostname        string
	env             []string
	mountInfo       []string
	stopSignal      string
	extraHostsValue []string
	dns             []string
	gateway         string
	cidr            string
	mac             string
	bridge          string
	ipAddr          string
	inChan          = make(chan []byte)
	outChan         = make(chan []byte)
	once            sync.Once
	detachKeys      string
	exposedPorts    []string

	cgroupsSetting CgroupsSetting
)

func GetRunCmd() *cobra.Command {
	runCmd := &cobra.Command{
		Use:   "run name [command]",
		Short: "run a command",
		Long:  `Run a command in a new container`,
		Args:  cobra.MinimumNArgs(1),
		Run:   handle,
	}

	runCmd.Flags().StringSliceVarP(&env, "env", "e", []string{}, "Set environment variables")
	runCmd.Flags().StringVar(&hostname, "hostname", "", "Container host name")
	runCmd.Flags().StringArrayVar(&mountInfo, "mount", []string{}, "Bind mount a volume")
	runCmd.Flags().StringVar(&stopSignal, "stop-signal", "SIGTERM", " Signal to stop a container")
	runCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Keep STDIN open even if not attached")
	runCmd.Flags().BoolVarP(&tty, "tty", "t", false, "Allocate a pseudo-TTY")
	runCmd.Flags().BoolVarP(&detach, "detach", "d", false, "Run container in background and print container ID")
	runCmd.Flags().StringSliceVar(&extraHostsValue, "add-host", []string{}, "Add a custom host-to-IP mapping (host:ip)")
	runCmd.Flags().StringSliceVar(&dns, "dns", []string{}, "Set custom DNS servers")
	runCmd.Flags().StringVar(&mac, "mac", "", "set mac for your container")
	runCmd.Flags().StringVar(&bridge, "network", "", "set network for your container")
	runCmd.Flags().StringVar(&ipAddr, "ip", "", "set ip for your container")
	runCmd.Flags().StringVar(&name, "name", "", "Assign a name to the container")
	runCmd.Flags().StringVar(&detachKeys, "detach-keys", "ctrl-p,ctrl-q", "Override the key sequence for detaching a container.")
	runCmd.Flags().StringArrayVar(&exposedPorts, "exposed-ports", nil, "Publish a container's port(s) to the host")

	flags := runCmd.Flags()
	flags.StringVar(&cgroupsSetting.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
	flags.StringVar(&cgroupsSetting.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
	flags.Int64Var(&cgroupsSetting.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
	flags.Int64Var(&cgroupsSetting.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
	flags.Int64Var(&cgroupsSetting.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds")
	flags.Int64Var(&cgroupsSetting.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds")
	flags.Int64VarP(&cgroupsSetting.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
	flags.StringVar(&cgroupsSetting.kernelMemory, "kernel-memory", "", "Kernel memory limit")
	flags.StringVarP(&cgroupsSetting.memoryString, "memory", "m", "", "Memory limit")
	flags.StringVar(&cgroupsSetting.memoryReservation, "memory-reservation", "", "Memory soft limit")
	flags.StringVar(&cgroupsSetting.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
	flags.Int64Var(&cgroupsSetting.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)")
	flags.BoolVar(&cgroupsSetting.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer")
	flags.StringVar(&cgroupsSetting.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")

	return runCmd
}

func handle(cmd *cobra.Command, args []string) {

	id := args[0]

	id = image.CheckNameOrId(id)

	//volumes
	volumes := container.MountHandle(mountInfo)

	//ExtraHost
	extraHostsParam := container.ExtraHostDeal(extraHostsValue)

	//ExposedPorts
	err, exposedPortsStruct := container.ExposedPortsHandle(exposedPorts)
	if err != nil {
		fmt.Printf("Cannot create the container for reason %v\n", err)
		return
	}

	//获得网络信息
	var ip net.IP
	networkInfo := network.GetNetworkInfo(bridge)
	if networkInfo != nil {
		cidr = networkInfo.Config.Subnet
		gateway = networkInfo.Config.Gateway
		bridge = networkInfo.Driver
		if ipAddr == "" {
			var err error
			ip, err = network.AllocateIP(bridge)
			if err != nil {
				fmt.Println("ip allocate failed")
				return
			}

			defer func() {
				if err := network.ReleaseIP(bridge, ip); err != nil {
					fmt.Println("ip release failed")
				}
			}()
		} else {
			ip = net.ParseIP(ipAddr)
			if err := network.RegisterIP(bridge, ip); err != nil {
				fmt.Println(err)
			}
		}
		ipAddr = ip.String()
	}

	//cidr
	networkAddress := []*pb.NetworkAddress{}
	mask := 0

	if cidr != "" {
		reg, err := regexp.Compile(`([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/([0-9]{0,2})`)
		if err != nil {
			fmt.Println(err)
			return
		}

		subStr := reg.FindAllStringSubmatch(cidr, 1)[0]

		mask, err = strconv.Atoi(subStr[2])

		if err != nil {
			fmt.Println(err)
			return
		}

		networkAddress = append(networkAddress, &pb.NetworkAddress{
			Ip:   ipAddr,
			Mask: int32(mask),
		})
	}

	// interface
	var interfaceSlice []*pb.NetworkInterface

	//network interfaces
	if !(bridge == "" || gateway == "" || len(networkAddress) == 0) {
		networkInterfaces := pb.NetworkInterface{
			Name:    "",
			Bridge:  bridge,
			Mac:     mac,
			Gateway: gateway,
			Address: networkAddress,
		}
		interfaceSlice = append(interfaceSlice, &networkInterfaces)
	} else if (bridge == "" || gateway == "" || len(networkAddress) == 0) && !(bridge == "" && gateway == "" && len(networkAddress) == 0) {
		container.PrintContainerNetworkError()
		return
	} else {
	}

	// 设置环境变量
	defaultEnv := []string{}
	term := os.Getenv("TERM")
	defaultEnv = append(defaultEnv, "TERM="+term)
	env = append(env, defaultEnv...)

	//检查args长度
	command := make([]string, 0)
	if len(args) >= 2 {
		command = args[1:]
	}

	resources, err := parse()
	r, err := connection.Client.CreateContainer(context.Background(), &pb.CreateContainerReq{
		ImageId: id,
		Name:    name,
		Network: &pb.Network{
			Hostname:   hostname,
			ExtraHosts: extraHostsParam,
			Dns:        dns,
			Interfaces: interfaceSlice,
		},
		Env:          env,
		Volumes:      volumes,
		StopSignal:   stopSignal,
		Tty:          tty,
		Cmd:          command,
		Resources:    resources,
		ExposedPorts: exposedPortsStruct,
	})

	if err != nil {
		fmt.Printf("Cannot create the container for reason %v\n", err)
		return
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer func() {
		cancel()
	}()

	group := &sync.WaitGroup{}
	// attach
	if !detach {
		stream, err := connection.Client.AttachContainer(context.Background())

		if err != nil {
			fmt.Printf("Cannot attach to the container for reason %v\n", err)
		} else {
			if err := stream.Send(&pb.AttachStreamIn{
				Id:      r.Id,
				Content: []byte{},
			}); err != nil {
				fmt.Println(err)
			}

			opts := io.AttachOptions{
				Tty:          tty,
				AttachStdin:  interactive,
				AttachStdout: !detach,
				Id:           r.Id,
				EscapeKeys:   detachKeys,
				Container:    true,
			}

			islandsIo := io.IslandsIO{
				In:     streams.NewIn(os.NewFile(uintptr(0), "/dev/tty")),
				Out:    streams.NewOut(os.Stdout),
				Opts:   opts,
				GrpcIO: *io.NewGrpcIO(r.Id, stream),
			}

			group.Add(1)
			go func(waitGroup *sync.WaitGroup) {
				islandsIo.Stream(ctx, waitGroup)
			}(group)
		}
	}

	// start container
	start_err, err := connection.Client.StartContainer(context.Background(), &pb.ContainerID{
		Id: r.Id,
	})

	if err != nil {
		fmt.Printf("Cannot start the container for reason %v\n", err)
		cancel()
	}

	if !utils.PrintError(start_err) {
		if detach {
			fmt.Println(r.Id)
		}
	}
	group.Wait()

}
